package com.ikingtech.platform.service.tenant.controller;

import com.ikingtech.framework.sdk.context.event.TenantDeleteEvent;
import com.ikingtech.framework.sdk.context.event.TenantInitEvent;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.enums.system.tenant.TenantStatusEnum;
import com.ikingtech.framework.sdk.tenant.api.TenantApi;
import com.ikingtech.framework.sdk.tenant.api.TenantUserApi;
import com.ikingtech.framework.sdk.tenant.model.TenantDTO;
import com.ikingtech.framework.sdk.tenant.model.TenantQueryParamDTO;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.tenant.entity.TenantDO;
import com.ikingtech.platform.service.tenant.exception.TenantExceptionInfo;
import com.ikingtech.platform.service.tenant.service.TenantService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

/**
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
@ApiController(value = "/tenant", name = "系统管理-租户管理", description = "系统管理-租户管理")
public class TenantController implements TenantApi {

    private final TenantService service;

    private final TenantUserApi tenantUserApi;

    private final ApplicationContext applicationContext;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<String> add(TenantDTO tenant) {
        if (this.service.nameExist(tenant.getName())) {
            throw new FrameworkException(TenantExceptionInfo.DUPLICATE_TENANT_NAME);
        }
        if (this.service.codeExist(tenant.getCode())) {
            throw new FrameworkException(TenantExceptionInfo.DUPLICATE_TENANT_NAME);
        }
        TenantDO entity = Tools.Bean.copy(tenant, TenantDO.class);
        entity.setId(Tools.Id.uuid());
        entity.setSortOrder(this.service.getMaxSortOrder() + 1);
        this.service.checkStatusAndPeriod(entity);
        this.service.save(entity);
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                applicationContext.publishEvent(new TenantInitEvent(this, entity.getCode(), entity.getName()));
            }
        });
        return R.ok(entity.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<Object> delete(String code) {
        this.service.removeByCode(code);
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                applicationContext.publishEvent(new TenantDeleteEvent(this, code));
            }
        });
        return R.ok();
    }

    @Override
    public R<Object> update(TenantDTO tenant) {
        if (!this.service.exist(tenant.getId())) {
            throw new FrameworkException(TenantExceptionInfo.TENANT_NOT_FOUND);
        }
        if (this.service.nameExist(tenant.getId(), tenant.getName())) {
            throw new FrameworkException(TenantExceptionInfo.DUPLICATE_TENANT_NAME);
        }
        if (this.service.codeExist(tenant.getId(), tenant.getCode())) {
            throw new FrameworkException(TenantExceptionInfo.DUPLICATE_TENANT_CODE);
        }
        TenantDO entity = Tools.Bean.copy(tenant, TenantDO.class);
        this.service.checkStatusAndPeriod(entity);
        this.service.updateById(entity);
        return R.ok();
    }

    @Override
    public R<List<TenantDTO>> page(TenantQueryParamDTO queryParam) {
        return R.ok(this.service.listPage(queryParam).convertBatch(this::modelConvert));
    }

    @Override
    public R<TenantDTO> detail(String id) {
        return R.ok(this.modelConvert(this.service.getById(id)));
    }

    @Override
    public R<TenantDTO> getByCode(String code) {
        return R.ok(this.modelConvert(this.service.getByCode(code)));
    }

    @Override
    public R<TenantDTO> getByLoginUser() {
        return R.ok(this.modelConvert(this.service.getByCode(Me.tenantCode())));
    }

    @Override
    public R<List<TenantDTO>> listByLoginUser() {
        List<String> codes = this.tenantUserApi.loadCodeByUserId(Me.id());
        if (Tools.Coll.isBlank(codes)) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(this.modelConvert(this.service.listByCodes(codes)));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<Object> retry(String tenantId) {
        TenantDO entity = this.service.getById(tenantId);
        if (entity == null) {
            throw new FrameworkException(TenantExceptionInfo.TENANT_NOT_FOUND);
        }
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                applicationContext.publishEvent(new TenantInitEvent(this, entity.getCode(), entity.getName()));
            }
        });
        return R.ok();
    }

    @Scheduled(cron = "0 0 0 * * ?")
    @Transactional(rollbackFor = Exception.class)
    public void checkStatus() {
        List<TenantDO> entities = this.service.list();
        if (Tools.Coll.isBlank(entities)) {
            return;
        }
        this.service.updateBatchById(Tools.Coll.traverse(entities, entity -> {
            if (null != entity.getEndDate() && LocalDate.now().isAfter(entity.getEndDate())) {
                entity.setStatus(TenantStatusEnum.FREEZE.name());
            }
            if (null != entity.getStartDate() && !LocalDate.now().isBefore(entity.getStartDate())) {
                entity.setStatus(TenantStatusEnum.NORMAL.name());
            }
            return entity;
        }));
    }

    private TenantDTO modelConvert(TenantDO entity) {
        TenantDTO tenant = Tools.Bean.copy(entity, TenantDTO.class);
        if (null != tenant.getType()) {
            tenant.setTypeName(tenant.getType().description);
        }
        if (null != tenant.getStatus()) {
            tenant.setStatusName(tenant.getStatus().description);
        }
        return tenant;
    }

    private List<TenantDTO> modelConvert(List<TenantDO> entities) {
        return Tools.Coll.convertList(entities, this::modelConvert);
    }
}
